Skip to content

Feature/bitfield change detector factory#2

Draft
matt05d wants to merge 5 commits intomainfrom
feature/bitfield-change-detector-factory
Draft

Feature/bitfield change detector factory#2
matt05d wants to merge 5 commits intomainfrom
feature/bitfield-change-detector-factory

Conversation

@matt05d
Copy link
Collaborator

@matt05d matt05d commented Mar 25, 2026

Inline change detection for schemas with leveled bitfields

Summary

  • For groups containing bitfield points with level annotations (Warning/Error/Emergency), the C# source generator now emits inline change detection directly in the generated client class. This removes the
    need for any external change detection types — the generated code is fully self-contained.
  • A shadow buffer stores the previous read values for each affected group. After each Read/ReadAsync, the generator compares current and previous values per bitfield point and invokes an optional callback
    when changes are detected.
  • The callback is passed as an optional constructor parameter (Action<string, Level>? onBitfieldChanged = null). When null (the default), no comparison runs and no shadow buffer is checked — zero overhead
    for consumers that don't need change detection.
  • This approach keeps the Modspec schema language-agnostic. The JSON schema is unchanged, and the detection logic is derived entirely from the existing level annotations. Generators for other languages can
    implement equivalent detection using the same schema information.

Usage

Pass a callback when constructing the generated client:

  List<(string name, Level level)> changes = [];
  var client = new SomeBmsClient(modbusClient, 2, 2, 480, 100,
      onBitfieldChanged: (name, level) => changes.Add((name, level)));

  await client.ReadWarningsErrorsEmergenciesAsync();
  // callback fires automatically for each bitfield point that changed since the last read

Without a callback, the client works exactly as before — no behavioral change:

  var client = new SomeBmsClient(modbusClient, 2, 2, 480, 100);

What the generator emits

For a group WarningsErrorsEmergencies containing StringErrors1 and StringErrors2 (both bitfields with levels):

  private readonly Memory<byte> _bufferWarningsErrorsEmergencies;
  private readonly Memory<byte> _previousWarningsErrorsEmergencies;

  public async ValueTask ReadWarningsErrorsEmergenciesAsync()
  {
      await _client.ReadDiscreteInputsAsync(1, _bufferWarningsErrorsEmergencies);
      if (_onBitfieldChanged is not null) CheckWarningsErrorsEmergencies();
  }

  private void CheckWarningsErrorsEmergencies()
  {
      Span<byte> current = _bufferWarningsErrorsEmergencies.Span;
      Span<byte> previous = _previousWarningsErrorsEmergencies.Span;
      if (!current.Slice(0, 2).SequenceEqual(previous.Slice(0, 2)))
      {
          _onBitfieldChanged!("StringErrors1", StringErrors1.GetLevel());
      }
      if (!current.Slice(2, 2).SequenceEqual(previous.Slice(2, 2)))
      {
          _onBitfieldChanged!("StringErrors2", StringErrors2.GetLevel());
      }
      current.CopyTo(previous);
  }

Change detection also works for bitfield points inside repeating groups — the callback is passed through to each nested class instance.

For each schema with bitfield points that have level annotations, emit
a static factory method that returns a BitfieldChangeDetector configured
with the appropriate property getters and GetLevel calls. The runtime
change detection logic lives in the consuming project.
The source generator now emits a change detection factory that
references BitfieldChangeDetector<T>, which lives in the consuming
project. The test project needs a minimal stub so the generated
code compiles.
@matt05d matt05d marked this pull request as draft March 26, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants